package dkbta.ontology.elements.abstractions.trend;

import static dkbta.util.time.TimeUtils.convertMillisToTimeUnit;

import java.util.List;

import dkbta.ontology.elements.Element;
import dkbta.ontology.elements.abstractions.PairedMappingFunction;
import dkbta.ontology.elements.abstractions.interpolation.PersistenceFunction;
import dkbta.util.time.TimeUnit;
import dkbta.util.time.TimeUtils;

/**
 * A paired mapping function for trends
 * 
 * @author Uri Kanonov
 */
public class TrendMappingFunction extends PairedMappingFunction<Trend>{
	private static final String DECREASING = "Decreasing";

	private static final String SAME = "Same";

	private static final String INCREASING = "Increasing";

	/** The values that are required by a Trend abstraction */
	public static final String[] NECESSARY_VALUES = {DECREASING, SAME, INCREASING};

	/**
	 * The variation threshold that decides whether a trend between two primitives is
	 * decreasing, same or increasing
	 */
	private final double _significantVariation;

	/**
	 * The critical angle that decides whether a new primitive can be joined to an
	 * existing trend or it is too different
	 * 
	 * @see #angleAllowsConnection(Trend, Element)
	 */
	private final double _angle;

	/** The time unit based on which the angle was decided */
	private final TimeUnit _timeUnit;

	/**
	 * Initializes a trend mapping function
	 * 
	 * @param significantVariation The variation threshold that decides whether a trend between two primitives is
	 * decreasing, same or increasing
	 * @param angle The critical angle that decides whether a new primitive can be joined to an
	 * existing trend or it is too different
	 * @param timeUnit The time unit based per which are the angle and significantVariation
	 */
	public TrendMappingFunction(double significantVariation, double angle,
		TimeUnit timeUnit){
		_timeUnit = timeUnit;
		// Adjusting the significantVariation to be per millisecond
		_significantVariation = TimeUtils.convertFromTimeUnitToMillis(
			significantVariation, timeUnit);
		_angle = angle;
	}

	@Override
	public String toString(){
		StringBuilder sb = new StringBuilder();
		sb.append("Significant Variation: ").append(_significantVariation).append("\r\n");
		sb.append("Angle: ").append(_angle).append("\r\n");
		return sb.toString();
	}

	@Override
	protected String calculateValue(double variationPerMillisecond){
		// Determining the value of the gradient according to the significantVariation
		if (variationPerMillisecond > _significantVariation){
			return INCREASING;
		}else if (variationPerMillisecond < -_significantVariation){
			return DECREASING;
		}else{
			return SAME;
		}
	}

	@Override
	protected void mapElements(List<Trend> trends, String trendName, Element before,
		Element after, PersistenceFunction interpolation){
		// Attempting to add the current elements to the previous trend
		// if it wasn't discontinued
		Trend lastTrend;
		int numTrends = trends.size();
		if (numTrends > 0 && (lastTrend = trends.get(numTrends - 1)).getLast() == before){
			String value = mapPair((Double)before.getValue(), (Double)after.getValue(),
				lastTrend.getTimeInterval(), after.getTimeInterval(), interpolation);
			// Checking if the trend can be connected with the additional element
			// and that the angle with the new element is not too big. If it is too big, a
			// new trend needs to be created
			if (value == null){
				return;
			}else if (angleAllowsConnection(lastTrend, after)){
				lastTrend.setLast(after);
				lastTrend.setValue(value);
				return;
			}
		}

		// There is no previous trend or the angle with it was too big so we attempt
		// to create a trend from the given two elements
		String value = mapPair(before, after, interpolation);
		if (value != null){
			trends.add(new Trend(trendName, before.getTimeInterval().getStartTime(),
					after.getTimeInterval().getEndTime(), value, before, after));
		}
	}

	/**
	 * Checking whether the given primitive can be added to the given trend. That is,
	 * whether the angle between the vector (namely V) formed by the first and last
	 * primitives of the trend and the vector (namely U) formed by the new primitive and
	 * the last primitive of the trend is smaller than the angle from the ontology). The
	 * calculation is done according to the formula:
	 * <tr>cosAlpha=(V*U)/(|V|*|U|)</tr>
	 * <br>
	 * Note: V*U is the dot product
	 * 
	 * @return Whether the angle between U and V is smaller than the angle from the
	 *         ontology
	 */
	public boolean angleAllowsConnection(Trend trend, Element primitive){
		Element first = trend.getFirst();
		Element last = trend.getLast();
		long tLast = last.getTimeInterval().getEndTime();
		long tFirst = first.getTimeInterval().getEndTime();
		long tNew = primitive.getTimeInterval().getEndTime();
		double vLast = (Double)last.getValue();
		double vFirst = (Double)first.getValue();
		double vNew = (Double)primitive.getValue();

		double dtLF = convertMillisToTimeUnit(tLast - tFirst, _timeUnit);
		double dtNL = convertMillisToTimeUnit(tNew - tLast, _timeUnit);
		double dvLF = vLast - vFirst;
		double dvNL = vNew - vLast;

		double cosAlpha = (dtLF * dtNL + dvLF * dvNL)
				/ Math.sqrt((dtLF * dtLF + dvLF * dvLF) * (dtNL * dtNL + dvNL * dvNL));
		double alpha = Math.toDegrees(Math.acos(cosAlpha));

		return alpha <= _angle;
	}
}
